1
2
3 from
enum import Enum
4 import datetime
5 import random

6
7 """Constants"""

8 PLAYER =
'X'
9 COMPUTER =
'O'
10 EMPTY =
'_'
11 BOARD_SIZE =
5
12 NUMBER_OF_PLAYERS =
1
13 SEARCH_TIME =
5
14
15 """exception
class, in case the user entered a none empty position"""
16
17 class
NoneEmptyPosition(Exception):
18     pass

19
20
21 """exception
class, in case the user entered number higher the board positions"""
22
23
24 class
OutOfRange(Exception):
25     pass

26
27 """
enum for game state"""
28
29 class
GameState(Enum):
30     tie =
'Tie'
31     notEnd =
'notEnd'
32     o =
'O' # computer won
33     x =
'X' # player won
34
35
36 """

37 this
class managing the board game activities
38 """

39
40
41 class
Board:
42
43     
"""
44         the constructor gets
1 argumnet size - the size of the board
45         it initialized the game board to be empty board (size x size)
46         and store the last move which been made
in order to decide who won
47         """

48     def __init__(self, size):
49         self.mSize = size
50         self.mBoard = [[EMPTY
for x in range(size)] for y in range(size)]
51         self.lastMove = None
52
53     
"""this function prints the game board"""
54     def print(self):
55         
for i in range(self.mSize):
56             
for j in range(self.mSize):
57                 
if j < self.mSize-1:
58                     print(self.mBoard[i][j], end=
'|')
59                 
else:
60                     print(self.mBoard[i][j], end=
'')
61             print()
62
63     
"""this function gets position 0 - size x size
64             and convert it to a board position
65             and
return the match row and column"""
66     def getBoardPosition(self,position):
67         column = position%self.mSize
68         row = position
//self.mSize
69         
return row, column
70
71     
"""this function return the last move on the board"""
72     def getLastMove(self):
73         
return self.lastMove
74
75     
"""this function gets number of row in the board and return the match row"""
76     def getRow(self, numberOfRow):
77         
return self.mBoard[numberOfRow]
78
79     
"""this function gets number of column in the board and return the match column"""
80     def getColumn(self, numberOFColumn):
81         
return [row[numberOFColumn] for row in self.mBoard]
82
83     
"""this function return the diagonals of the board"""
84     def getDiagonal(self):
85         diagonal1 = [self.mBoard[i][i]
for i in range(self.mSize)]
86         diagonal2 = []
87         j =
0
88         
for i in reversed(range(self.mSize)):
89             diagonal2.append(self.mBoard[i][j])
90             j +=
1
91         
return diagonal1, diagonal2
92
93     
"""this function return the main diagonal of the board, left to right"""
94     def getMainDiagonal(self):
95         
return [self.mBoard[i][i] for i in range(self.mSize)]
96
97     
"""this function return the secondary diagonal of the board, right to left"""
98     def getSecondaryDiagonal(self):
99         diagonal = []
100         j =
0;
101         
for i in reversed(range(self.mSize)):
102             diagonal.append(self.mBoard[i][j])
103             j +=
1
104         
return diagonal
105
106     
"""this function gets position and checking if the position is on the main diagonal"""
107     def checkIfOnMainDiagonal(self, position):
108         
return position % (self.mSize + 1) == 0
109
110     
"""this function gets position and checking if the position is on the secondary diagonal"""
111     def checkIfOnSecondaryDiagonal(self, position):
112         
return position % (self.mSize - 1) == 0
113
114     
"""this function gets position and draw 'X' on the match place on the board"""
115     def drawX(self, position):
116         self.lastMove = position
117         (row, column) = self.getBoardPosition(position)
118         self.mBoard[row][column] = PLAYER
119
120     
"""this function gets position and draw '_' on the match place on the board"""
121     def drawEmpty(self, position):
122         (row, column) = self.getBoardPosition(position)
123         self.mBoard[row][column] = EMPTY
124
125     
"""this function gets position and draw 'O' on the match place on the board"""
126     def drawO(self, position):
127         self.lastMove = position
128         (row, column) = self.getBoardPosition(position)
129         self.mBoard[row][column] = COMPUTER
130
131     
"""this function gets position and checking if it empty"""
132     def checkIfRubricEmpty(self, position):
133         (row, column) = self.getBoardPosition(position)
134         
return self.mBoard[row][column] == EMPTY
135
136     
"""this function gets 2 arguments: listToBeChecked - line in the board
137             
char - 'X' or 'Y' and checking if all the line filled with it"""
138     def all_same(self, listToBeChecked,
char):
139         
return all(x == char for x in listToBeChecked)
140
141
142 """
this class handling all the game Activities"""
143
144
145 class
Game:
146     
"""the constructor gets 2 arguments: numberOfPlayers, board size
147         also, mNamesList - store the names of the players
148         mTurn - says who have the turn to play, even -player1, odd - player2
149         mComputerFirstPosition - store the random position of the computer"""

150
151     def __init__(self, numberOfPlayers, boardSize):
152         self.mBoard = Board(boardSize)
153         self.mBoardSize = boardSize
154         self.mNumberOfPlayers = numberOfPlayers
155         self.mNamesList = [
' ']*numberOfPlayers
156         self.mTurn = None
157         self.mComputerFirstPosition = None
158         self.coinFlip()
159         self.mBestMove =
0
160
161
162     
"""this function decide who is starting the game by coin flip,
163             
if the computer won it choose random Move for the first move"""
164     def coinFlip(self):
165         turn = random.choice([
'computer', 'player'])
166         
if turn == 'computer':
167             self.mComputerFirstPosition = random.randrange(self.mBoard.mSize **
2)
168             self.mTurn =
1
169         
else:
170             self.mTurn =
0
171
172     
"""this function gets the players names and stored them"""
173     def getPlayersNames(self):
174         counter =
1
175         
while counter <= self.mNumberOfPlayers:
176             
try:
177                 playerName = input(
'please enter the name of player' + str(counter))
178                 
if not playerName:
179                     raise ValueError(
"field cannot be empty, please enter name")
180                 
if not playerName.isalpha():
181                     raise ValueError(
"only letters are allowed")
182                 
if playerName in self.mNamesList:
183                     raise ValueError(
"name already chosen please choose different name")
184
185                 self.mNamesList[counter -
1] = playerName
186                 counter +=
1
187             except ValueError
as e:
188                 print(e)
189             except Exception:
190                 print(
"unknown error")
191
192
193     
"""this function gets the player move from the user"""
194     def getPlayerMove(self):
195         
while True:
196             
try:
197                 playerMove =
int(input(self.mNamesList[self.mTurn] + ' please select rubric'))
198                 
if not (0 <= playerMove <= (self.mBoardSize ** 2 - 1)):
199                     raise OutOfRange(
"Wrong position, please choose position 0 - " + str(self.mBoardSize ** 2 - 1))
200
201                 
if not self.mBoard.checkIfRubricEmpty(playerMove):
202                     raise NoneEmptyPosition(
"this rubric taken please choose other rubric")
203
204                 
return playerMove
205
206             except (OutOfRange, NoneEmptyPosition)
as e:
207                 print(e)
208             except ValueError
as e:
209                 print(
"only numbers are allowed")
210             except Exception:
211                 print(
"unknown error")
212
213     
""" this function gets a player turn and check if the player won
214         based
on his last move, it checking every line which including the last move"""
215     def checkForWin(self, turn):
216         
char = ''
217         
if turn % 2 == 0:
218             
char = 'X'
219         
else:
220             
char = 'O'
221         lastMove = self.mBoard.getLastMove()
222         row, col = self.mBoard.getBoardPosition(lastMove)
223
224         
if self.mBoard.all_same(self.mBoard.getRow(row), char) or \
225                 self.mBoard.all_same(self.mBoard.getColumn(col),
char):
226             
return True
227
228         
if self.mBoard.checkIfOnMainDiagonal(lastMove):
229             
if self.mBoard.all_same(self.mBoard.getMainDiagonal(), char):
230                 
return True
231
232         
if self.mBoard.checkIfOnSecondaryDiagonal(lastMove):
233             
if self.mBoard.all_same(self.mBoard.getSecondaryDiagonal(), char):
234                 
return True
235
236         
return False
237
238     
"""this function check if the game is tie, which means the board is filled and there is no winner"""
239     def checkForTie(self):
240         
for i in range(self.mBoard.mSize ** 2):
241             
if self.mBoard.checkIfRubricEmpty(i):
242                 
return False
243         
return True
244
245     
"""this function compute all the valid moves on the game board and return them"""
246     def genrate(self):
247         possibleMoves = []
248         
for i in range(self.mBoard.mSize ** 2):
249             
if self.mBoard.checkIfRubricEmpty(i):
250                 possibleMoves.append(i)
251         
return possibleMoves
252
253     
"""this function check the game state and return it"""
254     def checkGameState(self):
255         
if self.checkForWin(0):
256             
return GameState.x
257
258         
if self.checkForWin(1):
259             
return GameState.o
260
261         
if self.checkForTie():
262             
return GameState.tie
263
264         
return GameState.notEnd
265
266     
""" this function is starting the game and managing it until it over"""
267     def start(self):
268         self.getPlayersNames()
269         
while True:
270             self.mBoard.print()
271             self.mTurn %=
2
272             
if self.mTurn % 2 == 0:
273                 playerMove = self.getPlayerMove()
274                 self.mBoard.drawX(playerMove)
275             
else:
276                 print(
'computer please select rubric')
277                 
if self.mComputerFirstPosition is not None:
278                     computerMove = self.mComputerFirstPosition
279                     self.mComputerFirstPosition = None
280                 
else:
281                     computerMove = self.iterativeDeepSearch()
282
283                 self.mBoard.drawO(computerMove)
284
285             gameResult = self.checkGameState()
286             
if gameResult.value != 'notEnd':
287                 self.mBoard.print()
288                 
if gameResult.value == 'Tie':
289                     print(
'The game is tie')
290                 
else:
291                     
if self.mTurn % 2 == 0:
292                         print(self.mNamesList[self.mTurn] +
'is the winner!')
293                     
else:
294                         print(
'computer is the winner!')
295                 
break
296
297             self.mTurn +=
1
298
299     
""" this function gets 6 arguments: depth - the depth of the game tree
300         isMax - tell which side are we, the maximizer or the minimizer
301         alpha - store the best
value for the maximizer
302         beta - store the best
value for the minimizer
303         startTime - the time we started the search
304         timeLimit - the time we will search
for the best move
305         the function tells
if the moves I take is better or worse by compute the best score
306         and position
in the given depth, than it return them
307         I used minmax algorithm with alpha beta pruning"""

308     def minmax2(self, depth, isMax, alpha, beta, startTime, timeLimit):
309
310         moves = self.genrate()
311         score = self.evaluate()
312         position = None
313
314         
if datetime.datetime.now() - startTime >= timeLimit:
315             self.mTimePassed = True
316
317         
if not moves or depth == 0 or self.mTimePassed:
318             gameResult = self.checkGameState()
319             
if gameResult.value == 'X':
320                 
return -10**(self.mBoard.mSize+1), position
321             elif gameResult.
value == 'O':
322                 
return 10**(self.mBoard.mSize+1), position
323             elif gameResult.
value == 'Tie':
324                 
return 0, position
325
326             
return score, position
327
328         
if isMax:
329             
for i in moves:
330                     self.mBoard.drawO(i)
331                     score, dummy = self.minmax2(depth-
1, not isMax, alpha, beta, startTime, timeLimit)
332                     
if score > alpha:
333                         alpha = score
334                         position = i
335                         self.mBestMove = i
336
337                     self.mBoard.drawEmpty(i)
338                     
if beta <= alpha:
339                         
break
340
341             
return alpha, position
342         
else:
343             
for i in moves:
344                 self.mBoard.drawX(i)
345                 score, dummy = self.minmax2(depth-
1, not isMax, alpha, beta, startTime, timeLimit)
346                 
if score < beta:
347                     beta = score
348                     position = i
349                     self.mBestMove = i
350                 self.mBoard.drawEmpty(i)
351                 
if alpha >= beta:
352                     
break
353
354             
return beta, position
355
356     
"""this function search the best move it find in 5 seconds.
357        The function goes
as deep as possible in 5 second in the game tree
358        and
return the best move"""
359     def iterativeDeepSearch(self):
360         startTime = datetime.datetime.now()
361         endTime = startTime + datetime.timedelta(
0, SEARCH_TIME)
362         depth =
1
363         position = None
364         self.mTimePassed = False
365         
while True:
366             currentTime = datetime.datetime.now()
367             
if currentTime >= endTime:
368                 
break
369             best, position = self.minmax2(depth, True, -
10000000, 10000000, currentTime, endTime-currentTime)
370             depth +=
1
371
372         
if position is None:
373             position = self.mBestMove
374
375         
return position
376
377     
"""this function gets a board line and calculate how many
378         'X', 'O' and empty rubrics"""

379     def calculateLine(self, line):
380         oSum = line.count(COMPUTER)
381         xSum = line.count(PLAYER)
382         EmptySum = line.count(EMPTY)
383         
return oSum, xSum, EmptySum
384
385     
"""this function gets a board line and calculate it score"""
386     def getScoreLine(self, line):
387         score =
0
388         oSum, xSum, EmptySum = self.calculateLine(line)
389         
if xSum == 0 and oSum != 0:
390             
if oSum == self.mBoard.mSize:
391                 score +=
11 ** (oSum - 1)
392             score +=
10 ** (oSum - 1)
393         
if oSum == 0 and xSum != 0:
394             score += -(
10 ** (xSum - 1))
395         
return score
396
397     
"""this function evaluate the game board and return the score"""
398     def evaluate(self):
399         score =
0
400         
for i in range(self.mBoard.mSize):
401             score += self.getScoreLine(self.mBoard.getRow(i))
402             score += self.getScoreLine(self.mBoard.getColumn(i))
403
404         diagonals = self.mBoard.getDiagonal()
405         
for i in range(2):
406             score += self.getScoreLine(diagonals[i])
407         
return score
408
409
410 game = Game(NUMBER_OF_PLAYERS, BOARD_SIZE)
411 game.start()


Gõ tìm kiếm nhanh...